/// \file
/// \brief Contains HTTPConnection, used to communicate with web servers
///
/// This file is part of RakNet Copyright 2008 Kevin Jenkins.
///
/// Usage of RakNet is subject to the appropriate license agreement.
/// Creative Commons Licensees are subject to the
/// license found at
/// http://creativecommons.org/licenses/by-nc/2.5/
/// Single application licensees are subject to the license found at
/// http://www.jenkinssoftware.com/SingleApplicationLicense.html
/// Custom license users are subject to the terms therein.
/// GPL license users are subject to the GNU General Public
/// License as published by the Free
/// Software Foundation; either version 2 of the License, or (at your
/// option) any later version.

#include "TCPInterface.h"
#include "HTTPConnection.h"
#include "RakSleep.h"
#include "RakString.h"
#include "RakAssert.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>

using namespace RakNet;

HTTPConnection::HTTPConnection() : connectionState(CS_NONE)
{
	tcp=0;
}

void HTTPConnection::Init(TCPInterface* _tcp, const char *_host, unsigned short _port)
{
	tcp=_tcp;
	host=_host;
	port=_port;
}

void HTTPConnection::Post(const char *remote_path, const char *data, const char *_contentType)
{
	OutgoingPost op;
	op.contentType=_contentType;
	op.data=data;
	op.remotePath=remote_path;
	outgoingPosts.Push(op);
	//printf("Adding outgoing post\n");
}

bool HTTPConnection::HasBadResponse(int *code, RakNet::RakString *data)
{
    if(badResponses.IsEmpty())
        return false;

	if (code)
		*code = badResponses.Peek().code;
	if (data)
		*data = badResponses.Pop().data;
   return true;
}
void HTTPConnection::CloseConnection()
{
	if (incomingData.IsEmpty()==false)
	{
//		printf("\n\n----------------------- PUSHING -------------\n\n");
//		printf(incomingData.C_String());
//		printf("\n------------------------------------\n\n");
		//printf("Pushing result\n");
		results.Push(incomingData);
	}
	incomingData.Clear();
	tcp->CloseConnection(server);
	connectionState=CS_NONE;
//	printf("Disconnecting\n");
}
void HTTPConnection::Update(void)
{
	SystemAddress sa;
	sa = tcp->HasCompletedConnectionAttempt();
	while (sa!=UNASSIGNED_SYSTEM_ADDRESS)
	{
//		printf("Connected\n");
		connectionState=CS_CONNECTED;
		server=sa;
		sa = tcp->HasCompletedConnectionAttempt();
	}

	sa = tcp->HasFailedConnectionAttempt();
	while (sa!=UNASSIGNED_SYSTEM_ADDRESS)
	{
		//printf("Failed connected\n");
		CloseConnection();
		sa = tcp->HasFailedConnectionAttempt();
	}

	sa = tcp->HasLostConnection();
	while (sa!=UNASSIGNED_SYSTEM_ADDRESS)
	{
		//printf("Lost connection\n");
		CloseConnection();
		sa = tcp->HasLostConnection();
	}


	switch (connectionState)
	{
	case CS_NONE:
		{
			if (outgoingPosts.IsEmpty())
				return;

			//printf("Connecting\n");
			server = tcp->Connect(host, port, false);
			connectionState = CS_CONNECTING;
		}
		break;
	case CS_CONNECTING:
		{
		}
		break;
	case CS_CONNECTED:
		{
			//printf("Connected\n");
			if (outgoingPosts.IsEmpty())
			{
				//printf("Closed connection (nothing to do)\n");
				CloseConnection();
				return;
			}

			//printf("Sending request\n");
			currentProcessingRequest = outgoingPosts.Pop();
			RakString request("POST %s HTTP/1.0\r\n"
				"Host: %s\r\n"
				"Content-Type: %s\r\n"
				"Content-Length: %u\r\n"
				"\r\n"
				"%s",
				currentProcessingRequest.remotePath.C_String(),
				host.C_String(),
				currentProcessingRequest.contentType.C_String(),
				(unsigned) currentProcessingRequest.data.GetLength(),
				currentProcessingRequest.data.C_String());
	//		request.URLEncode();
			tcp->Send(request.C_String(), (unsigned int) request.GetLength(), server,false);
			connectionState=CS_PROCESSING;
		}
		break;
	case CS_PROCESSING:
		{
		}
	}

//	if (connectionState==CS_PROCESSING && currentProcessingRequest.data.IsEmpty()==false)
//		outgoingPosts.PushAtHead(currentProcessingRequest);
}
bool HTTPConnection::HasRead(void) const
{
	return results.IsEmpty()==false;
}
RakString HTTPConnection::Read(void)
{
	if (results.IsEmpty())
		return RakString();

	RakNet::RakString resultStr = results.Pop();
    // const char *start_of_body = strstr(resultStr.C_String(), "\r\n\r\n");
	const char *start_of_body = strpbrk(resultStr.C_String(), "\001\002\003%");
    
    if(! start_of_body)
    {
		return RakString();
    }

	// size_t len = strlen(start_of_body);
	//printf("Returning result with length %i\n", len);
	return RakNet::RakString::NonVariadic(start_of_body);
}
SystemAddress HTTPConnection::GetServerAddress(void) const
{
	return server;
}
void HTTPConnection::ProcessTCPPacket(Packet *packet)
{
	RakAssert(packet);

	// read all the packets possible
	if(packet->systemAddress == server)
	{
		if(incomingData.GetLength() == 0)
		{
			int response_code = atoi((char *)packet->data + strlen("HTTP/1.0 "));

			if(response_code > 299)
			{
				badResponses.Push(BadResponse(packet->data, response_code));
				//printf("Closed connection (Bad response 2)\n");
				CloseConnection();
				return;
			}
		}

		RakNet::RakString incomingTemp = RakNet::RakString::NonVariadic((const char*) packet->data);
		incomingTemp.URLDecode();
		incomingData += incomingTemp;

	//	printf((const char*) packet->data);
	//	printf("\n");

		RakAssert(strlen((char *)packet->data) == packet->length); // otherwise it contains Null bytes

		const char *start_of_body = strstr(incomingData, "\r\n\r\n");

		// besides having the server close the connection, they may
		// provide a length header and supply that many bytes
		if(start_of_body && connectionState == CS_PROCESSING)
		{
			/*
			// The stupid programmer that wrote this originally didn't think that just because the header contains this value doesn't mean you got the whole message
			if (strstr((const char*) packet->data, "\r\nConnection: close\r\n"))
			{
				CloseConnection();
			}
			else
			{
			*/
				long length_of_headers = (long)(start_of_body + 4 - incomingData.C_String());

				const char *length_header = strstr(incomingData, "\r\nLength: ");
				if(length_header)
				{
					long length = atol(length_header + 10) + length_of_headers;

					if((long) incomingData.GetLength() >= length)
					{
						//printf("Closed connection (Got all data due to length header)\n");
						CloseConnection();
					}
				}
			//}
		}
	}
}

bool HTTPConnection::IsBusy(void) const
{
	return connectionState != CS_NONE;
}

int HTTPConnection::GetState(void) const
{
	return connectionState;
}


HTTPConnection::~HTTPConnection(void)
{
	if (tcp)
		tcp->CloseConnection(server);
}


